package worker

import (
	"context"
	"crontab/common"
	"fmt"
	"github.com/gorhill/cronexpr"
	"log"
	"time"
)

var (
	G_Scheduler *Scheduler
)

type  Scheduler struct {
	JobEventChan chan *common.JobEvent
	JobPlanTable map[string]*common.JobSchedulerPlan
	JobExecuteInfo map[string]*common.JobExecuteInfo
	JobExecutorResChan chan *common.JobExecutorResult
}
// 写入返回通道
func (s *Scheduler) PushJobResChan(jobRes *common.JobExecutorResult) {
	s.JobExecutorResChan <- jobRes
}

// 处理任务事件
func (s *Scheduler) handleJobEvent(jobEvent *common.JobEvent) {
	var (
		jobSchedulerPlan *common.JobSchedulerPlan
		err error
		isExist bool
		jobExecuteInfo *common.JobExecuteInfo
		isRunning bool
	)
	switch jobEvent.EventType {
	case common.JOB_EVENT_TYPE_SAVE:
		// 保存事件
		if jobSchedulerPlan,err = BuildJobSchedulerPlan(jobEvent.Job); err != nil {
			log.Printf("生成执行计划失败 " + err.Error())
			return
		}
		s.JobPlanTable[jobEvent.Job.JobName] = jobSchedulerPlan
	case common.JOB_EVENT_TYPE_DEL:
		// 删除事件
		// 判断该job是否存在
		if jobSchedulerPlan,isExist = s.JobPlanTable[jobEvent.Job.JobName];isExist {
			delete(s.JobPlanTable,jobEvent.Job.JobName)
		}
	case common.JOB_EVENT_TYPE_KILL:
		// 强杀任务
		// 判断任务是否正在执行
		jobExecuteInfo,isRunning = s.JobExecuteInfo[jobEvent.Job.JobName]
		if isRunning {
			fmt.Println("接收到强杀任务,任务名:" + jobEvent.Job.JobName)
			jobExecuteInfo.CancelFunc()
		}
	}
}

// 重新计算任务调度状态
func (s *Scheduler) TrySchedule() (schedulerAfter time.Duration) {
	var (
		jobSchedulerPlan *common.JobSchedulerPlan
		nearTime *time.Time
		now time.Time
	)

	// 如果任务列表为空的话,默认睡眠1秒
	if len(s.JobPlanTable) == 0 {
		schedulerAfter = time.Second * 1
		return
	}

	// 当前时间
	now = time.Now()

	// 遍历所有任务
	for _, jobSchedulerPlan = range s.JobPlanTable {
		// 下次执行时间在当前时间之前或则等于当前时间 -> 要执行了
		if jobSchedulerPlan.NextTime.Before(now) || jobSchedulerPlan.NextTime.Equal(now) {
			// TODO: 尝试执行任务
			//fmt.Println("尝试执行任务 :" + jobSchedulerPlan.Job.JobName)
			s.TryStartJob(jobSchedulerPlan)
			// 更新下次执行时间
			jobSchedulerPlan.NextTime = jobSchedulerPlan.Expr.Next(now)
		}
		// 计算下次最近的任务执行时间
		// 最近时间为空或则当前执行计划的下次执行时间在最近的执行时间之前
		if nearTime == nil || jobSchedulerPlan.NextTime.Before(*nearTime) {
			nearTime = &jobSchedulerPlan.NextTime
		}
	}

	// 下次调度时间间隔
	schedulerAfter = (*nearTime).Sub(now)
	return
}

// 尝试开始执行任务
func (s *Scheduler) TryStartJob(jobPlan *common.JobSchedulerPlan) {
	var (
		jobExecute *common.JobExecuteInfo
		isExists bool
	)
	if jobExecute,isExists = s.JobExecuteInfo[jobPlan.Job.JobName];isExists {
		//log.Printf("当前任务正在执行中了:" + jobPlan.Job.JobName)
		return
	}
	// 写入任务正在执行中
	jobExecute = BuildJobExecuteInfo(jobPlan)
	s.JobExecuteInfo[jobPlan.Job.JobName] = jobExecute
	// 开始执行任务
	G_Executor.ExecutorJob(jobExecute)
	//fmt.Println(jobPlan.Job.JobName +" 任务正在执行中,计划执行时间: " + jobExecute.PlanTime.String() + "任务真正执行时间: " + jobExecute.RealTime.String())
}

// 任务返回处理
func (s *Scheduler) HandleJobResult(jobRes *common.JobExecutorResult) {
	var (
		errStr string
		jobLog *common.JobLog
	)

	// 删除执行状态
	//fmt.Println("执行完毕,任务名: " + jobRes.ExecutorInfo.Job.JobName + " 返回值: " + strings.TrimSpace(string(jobRes.Output)) + "错误:" + errStr + " 开始时间:"+jobRes.StartTime.String() + " 结束时间:"+jobRes.EndTime.String())

	// 写入mongodb日志
	if jobRes.Err != common.ERR_LOCKED {
		// 上锁失败的错误,不记录日志
		errStr = ""
		if jobRes.Err != nil {
			errStr = jobRes.Err.Error()
		}
		jobLog = &common.JobLog{
			JobName:       jobRes.ExecutorInfo.Job.JobName,
			Command:       jobRes.ExecutorInfo.Job.Command,
			Err:           errStr,
			Output:        string(jobRes.Output),
			PlanTime:      jobRes.ExecutorInfo.PlanTime.UnixMilli(),
			SchedulerTime: jobRes.ExecutorInfo.RealTime.UnixMilli(),
			StartTime:     jobRes.StartTime.UnixMilli(),
			EndTime:       jobRes.EndTime.UnixMilli(),
		}
		G_LogSkin.JobLogChan <- jobLog
	}

	delete(s.JobExecuteInfo,jobRes.ExecutorInfo.Job.JobName)
}

// 调度协程
func (s *Scheduler) SchedulerLoop() {
	var (
		jobEvent *common.JobEvent
		schedulerAfter time.Duration
		schedulerTimmer *time.Timer
		jobRes *common.JobExecutorResult
	)

	schedulerAfter = s.TrySchedule()
	// 定时器
	schedulerTimmer = time.NewTimer(schedulerAfter)

	for {
		select {
		case  jobEvent = <-s.JobEventChan:
			// 对任务列表中的任务进行操作
			s.handleJobEvent(jobEvent)
		case <-schedulerTimmer.C:
			// 最近的任务到期了
		case jobRes = <-s.JobExecutorResChan:
			// 调用handleJobResult处理返回数据
			s.HandleJobResult(jobRes)
		}
		// 重新调度一次任务
		schedulerAfter = s.TrySchedule()
		schedulerTimmer.Reset(schedulerAfter)
	}
}

// 往Scheduler里面推送
func (s *Scheduler) PushJobEvent(job *common.JobEvent)  {
	s.JobEventChan <- job
}

// 初始化Scheduler
func InitScheduler() {
	var (
		scheduler *Scheduler
	)
	scheduler = &Scheduler{
		JobEventChan: make(chan *common.JobEvent,1000),
		JobPlanTable: make(map[string]*common.JobSchedulerPlan),
		JobExecuteInfo: make(map[string]*common.JobExecuteInfo),
		JobExecutorResChan:make(chan *common.JobExecutorResult,1000),
	}
	G_Scheduler = scheduler
	go G_Scheduler.SchedulerLoop()
}

// 生成执行计划
func BuildJobSchedulerPlan(job *common.Job) (jobSchedulerPlan *common.JobSchedulerPlan,err error) {

	var (
		expr *cronexpr.Expression
	)
	if expr,err  = cronexpr.Parse(job.Exp); err != nil {
		return
	}
	jobSchedulerPlan = &common.JobSchedulerPlan{
		Job:      job,
		Expr:     expr,
		NextTime: expr.Next(time.Now()),
	}
	return
}

// 构建一个执行状态
func BuildJobExecuteInfo(jobPlan *common.JobSchedulerPlan) (jobExecuteInfo *common.JobExecuteInfo) {
	var (
		ctx context.Context
		cancelFunc context.CancelFunc
	)
	jobExecuteInfo = &common.JobExecuteInfo{
		Job:      jobPlan.Job,
		PlanTime: jobPlan.NextTime,
		RealTime: time.Now(),
	}
	ctx,cancelFunc = context.WithCancel(context.TODO())
	jobExecuteInfo.Ctx = ctx
	jobExecuteInfo.CancelFunc = cancelFunc
	return
}